/*Scala code license:

 Copyright 2009/10 Osman Pub

 Licensed under the Apache License, Version 2.0 (the "License") you may not use
 this file except in compliance with the License.

 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software distributed
 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.

 */
/**
 Orginal java code credits:
 * @author Pepijn Van Eeckhoudt
 */
package demos

import com.sun.opengl.util.BufferUtil
import common._
import java.awt.event._
import javax.media.opengl._
import javax.media.opengl.glu.GLU


object Lesson17 {
  def main(args: Array[String]) {
    val glDisplay = GLDisplay("Lesson 17: 2D Texture Fonts")
    glDisplay addGLEventListener(new Renderer)
    glDisplay addKeyListener(new KeyAdapter {
      override def keyReleased(e: KeyEvent) =
        e getKeyCode match { case KeyEvent.VK_ESCAPE => glDisplay stop }
    })
    glDisplay start
  }

  private class Renderer extends GLEventListener {
    /** Called by the drawable to initiate OpenGL rendering by the client.
     * After all GLEventListeners have been notified of a display event, the
     * drawable will swap its buffers if necessary.
     * @param gLDrawable The GLAutoDrawable object.
     */
    private val glu = new GLU
    private val textures = new Array[Int](3)

    private var base = 0
    private var count1, count2: Float = 0
    private var stringBuffer = BufferUtil newByteBuffer(256)

    def display(glDrawable: GLAutoDrawable) {
      val gl = glDrawable.getGL
      gl glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

      gl glLoadIdentity;									// Reset The View;

      gl glBindTexture(GL GL_TEXTURE_2D, textures(1))			
      gl glTranslatef(0, 0, -5)						// Move Into The Screen 5 Units
      gl glRotatef(45, 0, 0, 1)		// Rotate On The Z Axis 45 Degrees (Clockwise)
      gl glRotatef(count1 * 30, 1, 1, 0)	// Rotate On The X & Y Axis By count1 (Left To Right)
      gl glDisable(GL GL_BLEND)					// Disable Blending Before We Draw In 3D
      gl glColor3f(1, 1, 1)							// Bright White

      gl glBegin(GL GL_QUADS)							// Draw Our First Texture Mapped Quad
      gl glTexCoord2d(0, 0)						// First Texture Coord
      gl glVertex2f(-1, 1)						// First Vertex
      gl glTexCoord2d(1, 0)						// Second Texture Coord
      gl glVertex2f(1, 1)						// Second Vertex
      gl glTexCoord2d(1, 1)						// Third Texture Coord
      gl glVertex2f(1, -1)						// Third Vertex
      gl glTexCoord2d(0, 1)						// Fourth Texture Coord
      gl glVertex2f(-1, -1)						// Fourth Vertex
      gl glEnd;										// Done Drawing The First Quad

      gl glRotatef(90, 1, 1, 0)		// Rotate On The X & Y Axis By 90 Degrees (Left To Right)

      gl glBegin(GL GL_QUADS)						// Draw Our Second Texture Mapped Quad
      gl glTexCoord2d(0, 0)						// First Texture Coord
      gl glVertex2f(-1, 1)						// First Vertex
      gl glTexCoord2d(1, 0)						// Second Texture Coord
      gl glVertex2f(1, 1)						// Second Vertex
      gl glTexCoord2d(1, 1)						// Third Texture Coord
      gl glVertex2f(1, -1)						// Third Vertex
      gl glTexCoord2d(0, 1)						// Fourth Texture Coord
      gl glVertex2f(-1, -1)						// Fourth Vertex
      gl glEnd;											// Done Drawing Our Second Quad

      gl glEnable(GL GL_BLEND)									// Enable Blending

      gl glLoadIdentity;									// Reset The View

      // Pulsing Colors Based On Text Position
      gl glColor3f(scala.math.cos(count1).asInstanceOf[Float],
                   scala.math.sin(count2).asInstanceOf[Float],
                   1 - 0.5f * scala.math.cos(count1 + count2).asInstanceOf[Float])
      glPrint(gl, (280 + 250 * scala.math.cos(count1)).asInstanceOf[Int],
              (235 + 200 * scala.math.sin(count2)).asInstanceOf[Int], "Nehe", 0)

      gl glColor3f((scala.math.sin(count2)).asInstanceOf[Float],
                    1 - 0.5f * scala.math.cos(count1 + count2).asInstanceOf[Float],
                    scala.math.cos(count1).asInstanceOf[Float])
      glPrint(gl, (280 + 230 * scala.math.cos(count2)).asInstanceOf[Int],
              (235 + 200 * scala.math.sin(count1)).asInstanceOf[Int], "OpenGL", 1)

      gl glColor3f(0, 0, 1)
      glPrint(gl, (240 + 200 * scala.math.cos((count2 + count1)) / 5).asInstanceOf[Int],
              2, "Giuseppe D'Agata", 0)

      gl glColor3f(1, 1, 1)
      glPrint(gl, (242 + 200 * scala.math.cos((count2 + count1) / 5)).asInstanceOf[Int],
              2, "Giuseppe D'Agata", 0)

      count1 += 0.01f										// Increase The First Counter
      count2 += 0.0081f										// Increase The Second Counter
    }

    /** Called when the display mode has been changed.  <B>!!
     *CURRENTLY UNIMPLEMENTED IN JOGL !!</B>
     * @param gLDrawable The GLAutoDrawable object.
     * @param modeChanged Indicates if the video mode has changed.
     * @param deviceChanged Indicates if the video device has changed.
     */
    def displayChanged(glDrawable: GLAutoDrawable, modeChanged: Boolean,
                       deviceChanged: Boolean) {}

    /** Called by the drawable immediately after the OpenGL context is
     * initialized for the first time. Can be used to perform one-time OpenGL
     * initialization such as setup of lights and display lists.
     * @param gLDrawable The GLAutoDrawable object.
     */
    def init(glDrawable: GLAutoDrawable) {
      val gl = glDrawable.getGL
      val tileNames = Array("demos/data/images/font.png",
                            "demos/data/images/bumps.png")

      def buildFont {
        base = gl glGenLists(256)								// Creating 256 Display Lists
        gl glBindTexture(GL GL_TEXTURE_2D, textures(0)) // Select Our Font Texture
        for(i <- 0 until 256) {
          val cx = (i & 15).asInstanceOf[Float] / 16
          val cy = (i / 16).asInstanceOf[Float] / 16

          gl glNewList(base + i, GL GL_COMPILE)				// Start Building A List
          gl glBegin(GL GL_QUADS)							// Use A Quad For Each Character
          gl glTexCoord2f(cx, 1 - cy - 0.0625f)			// Texture Coord (Bottom Left)
          gl glVertex2i(0, 0)						// Vertex Coord (Bottom Left)
          gl glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f)	// Texture Coord (Bottom Right)
          gl glVertex2i(16, 0)						// Vertex Coord (Bottom Right)
          gl glTexCoord2f(cx + 0.0625f, 1 - cy)			// Texture Coord (Top Right)
          gl glVertex2i(16, 16)						// Vertex Coord (Top Right)
          gl glTexCoord2f(cx, 1 - cy)					// Texture Coord (Top Left)
          gl glVertex2i(0, 16)						// Vertex Coord (Top Left)
          gl glEnd;									// Done Building Our Quad (Character)
          gl glTranslated(10, 0, 0)						// Move To The Right Of The Character
          gl glEndList									// Done Building The Display List
        }													// Loop Until All 256 Are Built
      }

      def loadGLTextures {
        val textImg = TextureReader readTexture("demos/data/images/bumps.png")
        gl glGenTextures(2, textures, 0)

        for(i <- 0 until 2) {
          val texture = TextureReader.readTexture(tileNames(i))
          //Create Nearest Filtered Texture
          gl glBindTexture(GL GL_TEXTURE_2D, textures(i))

          gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MAG_FILTER,
                             GL GL_LINEAR)
          gl glTexParameteri(GL GL_TEXTURE_2D, GL GL_TEXTURE_MIN_FILTER,
                             GL GL_LINEAR)

          gl glTexImage2D(GL GL_TEXTURE_2D, 0, GL GL_RGB, texture.width,
                          texture.height, 0,
          GL GL_RGB, GL GL_UNSIGNED_BYTE, texture.pixels)
        }
      }

      // init
      loadGLTextures; buildFont

      gl glShadeModel(GL GL_SMOOTH)               // Enable Smooth Shading
      gl glClearColor(0, 0, 0, 0)     // Black Background
      gl glClearDepth(1)                       // Depth Buffer Setup
      gl glEnable(GL GL_DEPTH_TEST) 							// Enables Depth Testing
      gl glDepthFunc(GL GL_LEQUAL) 								
      gl glHint(GL GL_PERSPECTIVE_CORRECTION_HINT, GL GL_NICEST) 	
      gl glEnable(GL GL_TEXTURE_2D)
    }

    /** Called by the drawable during the first repaint after the component has
     * been resized. The client can update the viewport and view volume of the
     * window appropriately, for example by a call to
     * GL.glViewport(int, int, int, int) note that for convenience the component
     * has already called GL.glViewport(int, int, int, int)(x, y, width, height)
     * when this method is called, so the client may not have to do anything in
     * this method.
     * @param gLDrawable The GLAutoDrawable object.
     * @param x The X Coordinate of the viewport rectangle.
     * @param y The Y coordinate of the viewport rectanble.
     * @param width The new width of the window.
     * @param height The new height of the window.
     */
    def reshape(glDrawable: GLAutoDrawable, x: Int, y: Int, width: Int,
                height: Int) {
      require(height != 0)

      val gl = glDrawable.getGL
      val h = width.asInstanceOf[Float] / height.asInstanceOf[Float]

      gl glMatrixMode(GL GL_PROJECTION)
      gl glLoadIdentity;
      glu gluPerspective(45, h, 1, 20)
      gl glMatrixMode(GL GL_MODELVIEW)
      gl glLoadIdentity
    }

    private def glPrint(gl: GL, x: Int, y: Int, msg: String, set: Int) {
      gl glBindTexture(GL GL_TEXTURE_2D, textures(0))	// Select Our Font Texture
      gl glDisable(GL GL_DEPTH_TEST)							// Disables Depth Testing
      gl glMatrixMode(GL GL_PROJECTION)					// Select The Projection Matrix
      gl glPushMatrix;										// Store The Projection Matrix

      gl glLoadIdentity;									// Reset The Projection Matrix
      gl glOrtho(0, 640, 0, 480, -1, 1)							// Set Up An Ortho Screen
      gl glMatrixMode(GL GL_MODELVIEW)						// Select The Modelview Matrix
      gl glPushMatrix;										// Store The Modelview Matrix

      gl glLoadIdentity;									// Reset The Modelview Matrix
      gl glTranslated(x, y, 0)					// Position The Text (0,0 - Bottom Left)
      gl glListBase(base - 32 + (128 * (if(set > 1) 1 else set)))						// Choose The Font Set (0 or 1)

      if (stringBuffer.capacity < msg.length)
        stringBuffer = BufferUtil.newByteBuffer(msg.length)

      stringBuffer.clear; stringBuffer.put(msg.getBytes); stringBuffer.flip

      gl glCallLists(msg.length, GL GL_BYTE, stringBuffer)			// Write The Text To The Screen
      gl glMatrixMode(GL GL_PROJECTION)						// Select The Projection Matrix
      gl glPopMatrix;										// Restore The Old Projection Matrix
      gl glMatrixMode(GL GL_MODELVIEW)							// Select The Modelview Matrix
      gl glPopMatrix;										// Restore The Old Projection Matrix
      gl glEnable(GL GL_DEPTH_TEST)							// Enables Depth Testing
    }
  }
}